﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using iTunesLib;
using System.Windows.Forms;
using iControl.ActivityHooks;

namespace iControl
{
    #region Enum Declaration
    public enum ControlCommand
    {
        None = 0,
        Play,
        PlayPause,
        Stop,
        NextTrack,
        BackTrack,
        PreviousTrack,
        NextAlbum,
        PreviousAlbum,
        ShowHideCurrentTrackInfo,
        ToggleShuffle,
        ToggleAllSongsRepeat,
        FastForward,
        Rewind,
        Rate0,
        Rate1,
        Rate2,
        Rate3,
        Rate4,
        Rate5,
        IncreaseVolume,
        DecreaseVolume,
        ToggleMute,
        IncreaseMasterVolume,
        DecreaseMasterVolume,
        ToggleMuteMasterVolume,
        OpenSettingsWindow,
        OpenQuickSelectWindow,
        OpenAboutWindow,
        /// <summary>
        /// iTunes or application requested to quit.
        /// </summary>
        Quit,
    }
    public enum InterfaceCommand
    {
        NewTrackStarted,
        /// <summary>
        /// Occures when the iTunes playback changes to Stopped or Paused. A string accourding to the status will be 
        /// passed with the Tag property.
        /// </summary>
        PlaybackChanged,
        RatingChanged,
        ShowSettingsWindow,
        ShowQuickSelectWindow,
        ToggleTrackInfoVisibility,
        ShowAboutWindow,
        /// <summary>
        /// iTunes or application requested to quit.
        /// </summary>
        Quit
    }
    #endregion

    public sealed class Controller
    {
        #region Variable Declaration
        private Keys _PressedModifiers;

        /// <summary>
        /// Used in lock-on block instead of class type to avoid deadlocks.
        /// </summary>
        private static object m_SyncRoot = new Object();
        #endregion

        #region Contructor
        private Controller()
        {
            this.ITunes = new iTunesAppClass();
            this.ITunes.OnPlayerPlayEvent += new _IiTunesEvents_OnPlayerPlayEventEventHandler(iTunes_OnPlayerPlayEvent);
            this.ITunes.OnPlayerPlayingTrackChangedEvent += new _IiTunesEvents_OnPlayerPlayingTrackChangedEventEventHandler(iTunes_OnPlayerPlayingTrackChangedEvent);
            this.ITunes.OnAboutToPromptUserToQuitEvent += new _IiTunesEvents_OnAboutToPromptUserToQuitEventEventHandler(iTunes_OnAboutToPromptUserToQuitEvent);

            this.KeyboardHook = new UserActivityHook(false, true);
            this.KeyboardHook.KeyDown += new System.Windows.Forms.KeyEventHandler(keyboardHook_KeyDown);
            this.KeyboardHook.KeyUp += new System.Windows.Forms.KeyEventHandler(keyboardHook_KeyUp);

            _PressedModifiers = Keys.None;
        }

        #endregion

        #region Event Declaration
        public event EventHandler<NewCommandReceivedEventArgs> NewCommandReceived;
        private void OnNewCommandReceived(NewCommandReceivedEventArgs e)
        {
            if( NewCommandReceived != null )
                NewCommandReceived(this, e);
        }
        #endregion

        #region Methods
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        public void Dispose()
        {
            this.KeyboardHook.Stop();
            this.KeyboardHook.KeyDown -= new System.Windows.Forms.KeyEventHandler(keyboardHook_KeyDown);
            this.KeyboardHook.KeyUp -= new System.Windows.Forms.KeyEventHandler(keyboardHook_KeyUp);

            try
            {
                this.ITunes.OnPlayerPlayEvent -= new _IiTunesEvents_OnPlayerPlayEventEventHandler(iTunes_OnPlayerPlayEvent);
                this.ITunes.OnPlayerPlayingTrackChangedEvent -= new _IiTunesEvents_OnPlayerPlayingTrackChangedEventEventHandler(iTunes_OnPlayerPlayingTrackChangedEvent);
                this.ITunes.OnAboutToPromptUserToQuitEvent -= new _IiTunesEvents_OnAboutToPromptUserToQuitEventEventHandler(iTunes_OnAboutToPromptUserToQuitEvent);
            }
            catch( Exception ex )
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
                System.Diagnostics.Debug.WriteLine(ex.StackTrace);
            }
        }

        /// <summary>
        /// Updates the current track rating.
        /// </summary>
        /// <param name="rating">The rating.</param>
        public void UpdateCurrentTrackRating(int rating)
        {
            this.ITunes.CurrentTrack.Rating = rating * 20;
        }

        /// <summary>
        /// Gets the playlist containing the currently targeted track. To get playlist information use CurrentPlaylist 
        /// property instead.
        /// </summary>        
        public IEnumerable<PlaylistItem> GetCurrentPlaylist()
        {
            return GetTracks(null);
        }

        /// <summary>
        /// Gets the tracks of the current iTunes source that matches the specified filter.
        /// </summary>
        /// <param name="filter">The text to filter the results with.</param>
        public IEnumerable<PlaylistItem> GetTracks(string filter)
        {
            return GetTracks(filter, ITPlaylistSearchField.ITPlaylistSearchFieldAll);
        }
        /// <summary>
        /// Gets the tracks of the current iTunes source that matches the specified filter.
        /// </summary>
        /// <param name="filter">The text to filter the results with.</param>
        public IEnumerable<PlaylistItem> GetTracks(string filter, ITPlaylistSearchField searchField)
        {
            var tracks = (string.IsNullOrEmpty(filter)) ? this.ITunes.CurrentPlaylist.Tracks : this.ITunes.CurrentPlaylist.Search(filter, searchField);
            foreach( IITTrack item in tracks )
            {
                yield return new PlaylistItem()
                {
                    AlbumName = item.Album,
                    ArtistName = item.Artist,
                    Rating = item.Rating / 20,
                    TrackName = item.Name,
                };
            }
        }

        /// <summary>
        /// Plays the specified track.
        /// </summary>
        /// <param name="track">The track to be played.</param>
        public void Play(PlaylistItem track)
        {
            var searchQuery = string.Format("{0} {1} {2}", track.ArtistName, track.AlbumName, track.TrackName);
            var iTunesTracks = this.ITunes.CurrentPlaylist.Search(searchQuery, ITPlaylistSearchField.ITPlaylistSearchFieldVisible);
            if( iTunesTracks != null )
                iTunesTracks[1].Play();
        }
        /// <summary>
        /// Plays the currently targeted track.
        /// </summary>
        public void Play()
        {
            this.ITunes.Play();
        }
        /// <summary>
        /// Plays the next album in the current playlist of the iTunes.
        /// </summary>
        public void PlayNextAlbum()
        {
            if( this.ITunes.CurrentTrack == null || this.ITunes.CurrentPlaylist == null )
                return;

            IITTrack trackInfo = this.CurrentTrack;
            int currentIndex = trackInfo.PlayOrderIndex;
            int maxCount = this.ITunes.CurrentPlaylist.Tracks.Count;
            string currentAlbum = trackInfo.Album;

            while( currentIndex < maxCount && trackInfo.Album == currentAlbum )
            {
                currentIndex++;
                trackInfo = this.ITunes.CurrentPlaylist.Tracks.get_ItemByPlayOrder(currentIndex);

            };

            trackInfo.Play();
        }

        /// <summary>
        /// Plays the previous album in the current playlist of the iTunes.
        /// </summary>
        public void PlayPreviousAlbum()
        {
            if( this.ITunes.CurrentTrack == null || this.ITunes.CurrentPlaylist == null )
                return;

            IITTrack trackInfo = this.CurrentTrack;
            int currentIndex = trackInfo.PlayOrderIndex;
            string currentAlbum = trackInfo.Album;

            if( currentIndex <= 1 )
                return;

            while( currentIndex > 1 && trackInfo.Album == currentAlbum )
            {
                currentIndex--;
                trackInfo = this.ITunes.CurrentPlaylist.Tracks.get_ItemByPlayOrder(currentIndex);

            };

            if( currentIndex > 1 )
            {
                currentAlbum = trackInfo.Album;
                while( currentIndex > 1 && trackInfo.Album == currentAlbum )
                {
                    currentIndex--;
                    trackInfo = this.ITunes.CurrentPlaylist.Tracks.get_ItemByPlayOrder(currentIndex);

                };

                trackInfo = this.ITunes.CurrentPlaylist.Tracks.get_ItemByPlayOrder(++currentIndex);
            }

            trackInfo.Play();
        }

        /// <summary>
        /// Toggles the playing/paused state of the current track.
        /// </summary>
        public void PlayPause()
        {
            this.ITunes.PlayPause();
        }

        /// <summary>
        /// Advances to the next track in the current playlist. 
        /// </summary>
        public void NextTrack()
        {
            this.ITunes.NextTrack();
        }

        /// <summary>
        /// Pauses the playback.
        /// </summary>
        public void Pause()
        {
            this.ITunes.Pause();
        }

        /// <summary>
        /// Returns to the previous track in the current playlist.
        /// </summary>
        public void PreviousTrack()
        {
            this.ITunes.PreviousTrack();
        }

        /// <summary>
        /// Stops playback. .
        /// </summary>
        public void StopPlaying()
        {
            this.ITunes.Stop();
        }

        /// <summary>
        /// Cheacks specified playlist with the current iTunes playlist.
        /// </summary>
        /// <param name="playlist">The playlist to compare with iTunes playlist.</param>
        /// <returns>
        /// 	<c>true</c> if the specified playlist is changed; otherwise, <c>false</c>.
        /// </returns>
        public bool IsPlaylistChanged(IList<PlaylistItem> playlist)
        {
            if( playlist.Count == 0 || playlist.Count != CurrentPlaylistTrackCount )
                return true;

            if( playlist[0].TrackName != this.ITunes.CurrentPlaylist.Tracks[1].Name )
                return true;

            if( playlist[playlist.Count - 1].TrackName != this.ITunes.CurrentPlaylist.Tracks[playlist.Count].Name )
                return true;

            return false;
        }

        /// <summary>
        /// Closes the iTunes.
        /// </summary>
        public void CloseITunes()
        {
            this.ITunes.Quit();
        }

        /// <summary>
        /// Determines whether specified playlist item is currently playing in iTunes.
        /// </summary>
        /// <param name="item">The item to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified playlist item is playing; otherwise, <c>false</c>.
        /// </returns>
        public bool IsNowPlaying(PlaylistItem item)
        {
            return (this.ITunes.CurrentTrack.Album == item.AlbumName && this.ITunes.CurrentTrack.Artist == item.ArtistName && this.ITunes.CurrentTrack.Name == item.TrackName);
        }

        /// <summary>
        /// Shows the iTunes.
        /// </summary>
        public void ShowITunes()
        {
            var iTunesProcess = System.Diagnostics.Process.GetProcessesByName("iTunes");
            if( iTunesProcess.Length > 0 )
            {
                var handle = iTunesProcess[0].MainWindowHandle;
                Win32.SwitchToThisWindow(handle, true);
            }
        }
        #endregion

        #region Event Handlers
        private void keyboardHook_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            if( _PressedModifiers != Keys.None )
            {

                ControlCommand command = KeyMapManager.DefaultInstance.GetCommand(e.KeyCode, e.Modifiers);
                if( command != ControlCommand.None )
                {
                    switch( command )
                    {
                        case ControlCommand.Play:
                            this.ITunes.Play();
                            break;
                        case ControlCommand.PlayPause:
                            this.ITunes.PlayPause();
                            if( !IsPlaying )
                                OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.PlaybackChanged, "PAUSED"));
                            break;
                        case ControlCommand.Stop:
                            this.ITunes.Stop();
                            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.PlaybackChanged, "STOPPED"));
                            break;
                        case ControlCommand.NextTrack:
                            this.ITunes.NextTrack();
                            break;
                        case ControlCommand.BackTrack:
                            this.ITunes.BackTrack();
                            break;
                        case ControlCommand.PreviousTrack:
                            this.ITunes.PreviousTrack();
                            break;
                        case ControlCommand.NextAlbum:
                            this.PlayNextAlbum();
                            break;
                        case ControlCommand.PreviousAlbum:
                            this.PlayPreviousAlbum();
                            break;
                        case ControlCommand.ShowHideCurrentTrackInfo:
                            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.ToggleTrackInfoVisibility, this.ITunes.CurrentTrack));
                            break;
                        case ControlCommand.ToggleShuffle:
                            this.ITunes.CurrentPlaylist.Shuffle = !this.ITunes.CurrentPlaylist.Shuffle;
                            break;
                        case ControlCommand.ToggleAllSongsRepeat:
                            this.ITunes.CurrentPlaylist.SongRepeat = (this.ITunes.CurrentPlaylist.SongRepeat == ITPlaylistRepeatMode.ITPlaylistRepeatModeOff) ? ITPlaylistRepeatMode.ITPlaylistRepeatModeAll : ITPlaylistRepeatMode.ITPlaylistRepeatModeOff;
                            break;
                        case ControlCommand.FastForward:
                            this.ITunes.FastForward();
                            break;
                        case ControlCommand.Rewind:
                            this.ITunes.Rewind();
                            break;
                        case ControlCommand.Rate0:
                            this.ITunes.CurrentTrack.Rating = 0;
                            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.RatingChanged, 0));
                            break;
                        case ControlCommand.Rate1:
                            this.ITunes.CurrentTrack.Rating = 20;
                            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.RatingChanged, 1));
                            break;
                        case ControlCommand.Rate2:
                            this.ITunes.CurrentTrack.Rating = 40;
                            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.RatingChanged, 2));
                            break;
                        case ControlCommand.Rate3:
                            this.ITunes.CurrentTrack.Rating = 60;
                            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.RatingChanged, 3));
                            break;
                        case ControlCommand.Rate4:
                            this.ITunes.CurrentTrack.Rating = 80;
                            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.RatingChanged, 4));
                            break;
                        case ControlCommand.Rate5:
                            this.ITunes.CurrentTrack.Rating = 100;
                            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.RatingChanged, 5));
                            break;
                        case ControlCommand.OpenSettingsWindow:
                            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.ShowSettingsWindow));
                            break;
                        case ControlCommand.OpenQuickSelectWindow:
                            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.ShowQuickSelectWindow));
                            break;
                        case ControlCommand.OpenAboutWindow:
                            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.ShowAboutWindow));
                            break;
                        case ControlCommand.Quit:
                            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.Quit, true));
                            break;
                    }
                }

                _PressedModifiers = Keys.None;
            }
        }
        private void keyboardHook_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            ControlCommand command = KeyMapManager.DefaultInstance.GetCommand(e.KeyCode, e.Modifiers);
            if( command != ControlCommand.None )
            {
                switch( command )
                {
                    case ControlCommand.IncreaseVolume:
                        this.ITunes.SoundVolume += 10;
                        return;
                    case ControlCommand.DecreaseVolume:
                        this.ITunes.SoundVolume -= 10;
                        return;
                    case ControlCommand.ToggleMute:
                        this.ITunes.Mute = !this.ITunes.Mute;
                        return;
                    case ControlCommand.IncreaseMasterVolume:
                        return;
                    case ControlCommand.DecreaseMasterVolume:
                        return;
                    case ControlCommand.ToggleMuteMasterVolume:
                        return;
                }
            }

            _PressedModifiers = e.Modifiers;
        }


        private void iTunes_OnAboutToPromptUserToQuitEvent()
        {
            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.Quit, false));
        }
        private void iTunes_OnPlayerPlayEvent(object iTrack)
        {
            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.NewTrackStarted, iTrack));
        }
        private void iTunes_OnPlayerPlayingTrackChangedEvent(object iTrack)
        {
            OnNewCommandReceived(new NewCommandReceivedEventArgs(InterfaceCommand.NewTrackStarted, iTrack));
        }
        #endregion

        #region Static Properties
        private static volatile Controller m_DefaultInstance;
        /// <summary>
        /// Gets default instance of Controller class.
        /// </summary>
        public static Controller DefaultInstance
        {
            get
            {
                if( m_DefaultInstance == null )
                {
                    //This double-check locking approach solves the thread concurrency problems while avoiding an exclusive 
                    //lock in every call to the Instance property method.
                    lock( m_SyncRoot )
                    {
                        if( m_DefaultInstance == null )
                            m_DefaultInstance = new Controller();
                    }
                }

                return m_DefaultInstance;
            }
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the instance of the iTunes class that the <seealso cref="Controller"/> uses.
        /// </summary>
        public iTunesAppClass ITunes { get; private set; }

        /// <summary>
        /// Gets the currently targeted track. Returns null (Nothing in Visual Basic) if there is no currently targeted track.
        /// </summary>
        public IITTrack CurrentTrack
        {
            get { return this.ITunes.CurrentTrack; }
        }

        /// <summary>
        /// Gets a value indicating whether iTunes is playing.
        /// </summary>       
        public bool IsPlaying
        {
            get { return (this.ITunes.PlayerState == ITPlayerState.ITPlayerStatePlaying); }
        }

        /// <summary>
        /// Gets the currently using keyboard hook.
        /// </summary>
        /// <value>The keyboard hook.</value>
        public ActivityHooks.UserActivityHook KeyboardHook { get; private set; }

        /// <summary>
        /// Gets the ID that identifies the playlist.
        /// </summary>
        /// <value>The current playlist id.</value>
        public int CurrentPlaylistId
        {
            get { return this.ITunes.CurrentPlaylist.playlistID; }
        }

        /// <summary>
        /// Gets the current playlist track count.
        /// </summary>
        public int CurrentPlaylistTrackCount
        {
            get { return this.ITunes.CurrentPlaylist.Tracks.Count; }
        }

        /// <summary>
        /// Gets the playlist containing the currently targeted track. Note that this property gets the original iTunes COM 
        /// playlist. To get the playlist that contains <see cref="PlaylistItem"/>s, use GetCurrentPlaylist() method instead.
        /// </summary>
        public IITPlaylist CurrentPlaylist
        {
            get { return this.ITunes.CurrentPlaylist; }
        }
        #endregion
    }

    #region NewCommandReceived EventArgs
    public class NewCommandReceivedEventArgs : EventArgs
    {
        public NewCommandReceivedEventArgs(InterfaceCommand command)
        {
            m_Command = command;
        }
        public NewCommandReceivedEventArgs(InterfaceCommand command, object tag)
            : this(command)
        {
            m_Tag = tag;
        }

        private InterfaceCommand m_Command;
        public InterfaceCommand Command
        {
            get { return m_Command; }
        }

        private object m_Tag;
        public object Tag
        {
            get { return m_Tag; }
        }
    }
    #endregion
}
